"""
====================================================================
Copyright (C) 2012-2013 Eric Blond

This file is part of Neptune.

Neptune is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Neptune is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Neptune.  If not, see <http://www.gnu.org/licenses/>.
====================================================================

Created on Nov 24, 2012

@author: Eric
"""

import asyncore
import socket
import struct
import cPickle as pickle
import urlparse
from . import page
from . import reactor

#######

class Dispatcher(asyncore.dispatcher_with_send):
    
    RECV_BUFFER_SIZE = 8192
    
    def __init__(self, disp_map, callback):
        super(Dispatcher, self).__init__(map = disp_map)
        self._callback = callback
        self._prelude = ''
        self._count = None
        self._accu = []

    def send_value(self, value):
        data = pickle.dumps(value)
        self.send(struct.pack('!I', len(data)) + data)

    def handle_read(self):
        if self._callback:
            message = self.recv(self.RECV_BUFFER_SIZE)
            len_message = len(message)
            while True:
                if self._count is None:
                    len_prelude = len(self._prelude)
                    if (len_message + len_prelude) < 4:
                        self._prelude += message
                        return
                    self._count = struct.unpack(self._prelude + message[:4 - len_prelude])
                    message = message[4 - len_prelude:]
                    len_message -= 4 - len_prelude
                    self._prelude = ''
                if len_message < self._count:
                    self._accu.append(message)
                    self._count -= len_message
                    return
                self._accu.append(message[:self._count])
                value = pickle.loads(''.join(self._accu))
                self._callback(value)
                message = message[self._count:]
                len_message -= self._count
                self._count = None
                self._accu = []

#######

class Handler(page.Page):

    DispatcherMap = None
    Callback = None

    def start(self):
        asyncore.loop(map = self.DispatcherMap)
        if self.Callback:
            self.Callback()

#######

class Channel(page.Page):
    
    Url = None

    @page.cell
    def _UrlParseObject(self):
        return urlparse.urlparse(self.Url)

    @page.cell
    def UrlHostname(self):
        return self._UrlParseObject.hostname
    
    @page.cell
    def UrlPort(self):
        return self._UrlParseObject.port
    
    @page.cell
    def UrlScheme(self):
        return self._UrlParseObject.scheme.lower()

    @page.cell(initable = True)
    def OnReceive(self):
        return None

    @page.cell(initable = True)
    def OnDisconnect(self):
        return None
    
    @page.cell
    def _LiftedOnReceive(self):
        return reactor.reactor().lift(self.OnReceive)

    @page.cell
    def _LiftedOnDisconnect(self):
        return reactor.reactor().lift(self.OnDisconnect)

    _Dispatcher = None
    _Reactor = None

    def connect(self):
        if not self.UrlScheme:
            raise ValueError('No scheme selected')
        if self.UrlScheme != 'tcp':
            raise ValueError('Scheme "%s" is not supported' % self.URLScheme)
        if not(self.UrlHostname):
            raise ValueError('Hostname must be provided')
        if not(self.UrlPort):
            raise ValueError('Port must be provided')
        dispatcher_map = {}
        self._Dispatcher = Dispatcher(dispatcher_map, self._LiftedOnReceive)
        self._Dispatcher.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self._Dispatcher.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self._Dispatcher.connect(self.UrlHostname, self.UrlPort)
        self._Reactor = reactor.Reactor(Handler)
        self._Reactor.start(DispatcherMap = dispatcher_map,
                            Callback = self._LiftedOnDisconnect)
        self._Reactor.handler(sync = False).start()

    def send(self, value):
        self._Dispatcher.send_value(value)
    
    def disconnect(self):
        self._Dispatcher.close()
        if self._Reactor:
            self._Reactor.join()
            self._Reactor = None
        self._Dispatcher = None
